
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Windows.Forms;

using Derm.Render;

namespace Derm.Ui
{
	/// <summary>
	/// Mouse input abstraction.
	/// </summary>
	public abstract class MouseInput : RenderKernelService
	{
		#region Constructors

		/// <summary>
		/// 
		/// </summary>
		public MouseInput() : base("Derm.Ui.MouseInput", ExecutionModel.Context)
		{
			
		}

		#endregion

		#region Mouse Events Handlers

		/// <summary>
		/// Handle a mouse button pressed event.
		/// </summary>
		/// <param name="args">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event arguments.
		/// </param>
		public virtual void MouseDown(System.Windows.Forms.MouseEventArgs args) { }

		/// <summary>
		/// Handle a mouse button unpressed event.
		/// </summary>
		/// <param name="args">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event arguments.
		/// </param>
		public virtual void MouseUp(System.Windows.Forms.MouseEventArgs args) { }

		/// <summary>
		/// Handle a mouse moved event.
		/// </summary>
		/// <param name="args">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event arguments.
		/// </param>
		/// <param name="dx">
		/// A <see cref="System.Int32"/> that specifies the mouse relative movement on X axis.
		/// </param>
		/// <param name="dy">
		/// A <see cref="System.Int32"/> that specifies the mouse relative movement on Y axis
		/// </param>
		public virtual void MouseMove(System.Windows.Forms.MouseEventArgs args, int dx, int dy) { }

		/// <summary>
		/// Handle a mouse wheel rotated event.
		/// </summary>
		/// <param name="args">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event arguments.
		/// </param>
		public virtual void MouseWheel(System.Windows.Forms.MouseEventArgs args) { }

		#endregion

		#region Mouse Event Queue

		/// <summary>
		/// Supported event types.
		/// </summary>
		private enum EventType
		{
			/// <summary>
			/// Mouse button is pressed.
			/// </summary>
			ButtonDown,
			/// <summary>
			/// Mouse button is unpressed.
			/// </summary>
			ButtonUp,
			/// <summary>
			/// Mouse is moved.
			/// </summary>
			Move,
			/// <summary>
			/// Mouse wheel is rotated.
			/// </summary>
			Wheel
		}

		/// <summary>
		/// Mouse event.
		/// </summary>
		private struct Event
		{
			/// <summary>
			/// Event type.
			/// </summary>
			public EventType mType;
			/// <summary>
			/// Event sender.
			/// </summary>
			public object mSender;
			/// <summary>
			/// Event arguments.
			/// </summary>
			public MouseEventArgs mArgs;
		}

		/// <summary>
		/// Handle mouse button down event.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="object"/> which has raised the event.
		/// </param>
		/// <param name="e">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event
		/// arguments.
		/// </param>
		internal void MouseDownHandler(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			Event evt;

			// Define event
			evt.mType = EventType.ButtonDown;
			evt.mSender = sender;
			evt.mArgs = e;
			// Enqueue event
			lock (mEvents) {
				mEvents.AddLast(evt);
			}
		}

		/// <summary>
		/// Handle mouse button up events.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="object"/> which has raised the event.
		/// </param>
		/// <param name="e">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event
		/// arguments.
		/// </param>
		internal void MouseUpHandler(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			Event evt;

			// Define event
			evt.mType = EventType.ButtonUp;
			evt.mSender = sender;
			evt.mArgs = e;
			// Enqueue event
			lock (mEvents) {
				mEvents.AddLast(evt);
			}
		}

		/// <summary>
		/// Handle mouse move events.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="object"/> which has raised the event.
		/// </param>
		/// <param name="e">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event
		/// arguments.
		/// </param>
		internal void MouseMoveHandler(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			Event evt;

			// Define event
			evt.mType = EventType.Move;
			evt.mSender = sender;
			evt.mArgs = e;
			// Enqueue event
			lock (mEvents) {
				mEvents.AddLast(evt);
			}
		}

		/// <summary>
		/// Handle mouse wheel events.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="object"/> which has raised the event.
		/// </param>
		/// <param name="e">
		/// A <see cref="System.Windows.Forms.MouseEventArgs"/> that specifies the event
		/// arguments.
		/// </param>
		internal void MouseWheelHandler(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			Event evt;

			// Define event
			evt.mType = EventType.Wheel;
			evt.mSender = sender;
			evt.mArgs = e;
			// Enqueue event
			lock (mEvents) {
				mEvents.AddLast(evt);
			}
		}

		/// <summary>
		/// Enqueued mouse input events.
		/// </summary>
		private LinkedList<Event> mEvents = new LinkedList<Event>();

		#endregion

		#region RenderKernelContextService Implementation

		/// <summary>
		/// Mouse state.
		/// </summary>
		private struct MouseState
		{
			/// <summary>
			/// Pressed mouse buttons.
			/// </summary>
			public MouseButtons Buttons;
			/// <summary>
			/// Mouse wheel absolute position.
			/// </summary>
			public int Wheel;
			/// <summary>
			/// Mouse X axis movement delta.
			/// </summary>
			public int X;
			/// <summary>
			/// Mouse Y axis movement delta.
			/// </summary>
			public int Y;
		}

		/// <summary>
		/// Dequeue every
		/// </summary>
		/// <param name="ctx">
		/// </param>
		public override void ContextExecute(RenderContext ctx)
		{
			lock (mEvents) {
				foreach (Event e in mEvents) {
					int x = e.mArgs.X-mMouseState.X, y = e.mArgs.Y-mMouseState.Y;

					// Update mouse position
					mMouseState.X = e.mArgs.X;
					mMouseState.Y = e.mArgs.Y;

					// Execute event handler
					switch (e.mType) {
					case EventType.ButtonDown:
						// Update mouse state
						mMouseState.Buttons |= e.mArgs.Button;
						// Execute the event
						MouseDown(e.mArgs);
						break;
					case EventType.ButtonUp:
						// Update mouse state
						mMouseState.Buttons &= ~e.mArgs.Button;
						// Handle the event
						MouseUp(e.mArgs);
						break;
					case EventType.Move:
						// Handle the event
						MouseMove(e.mArgs, x, y);
						break;
					case EventType.Wheel:
						// Update mouse state
						mMouseState.Wheel += e.mArgs.Delta;
						// Handle the event
						MouseWheel(e.mArgs);
						break;
					}
				}
				mEvents.Clear();
			}
		}

		/// <summary>
		/// Current mouse input state.
		/// </summary>
		/// <remarks>
		/// The current state is maintained in order to implement correcty the mouse
		/// handler interface.
		/// </remarks>
		private MouseState mMouseState;

		#endregion
	}
}
